package com.jacky.ocr_plugin.camera;

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.params.SessionConfiguration;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Size;
import android.view.Surface;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;

import com.jacky.util.OcrLogger;
import com.jacky.util.OcrUtil;
import com.jacky.util.OcrOrientationEventListener;
import com.jacky.util.OnOcrRectErrorCallback;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;

import io.flutter.view.TextureRegistry;

/**
 * 相机预览，提供flutter的textureId，并控制相机方法调用
 */
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class CameraView implements ICameraView{

    //相机名称：前置摄像头1，后置摄像头0
    public static final String CAMERA_FRONT = "1";
    public static final String CAMERA_BACK = "0";
    //图片数据回调
    private OnImageListener mOnImageListener;
    private final OcrOrientationEventListener orientationListener;
    //后台进程及处理数据
    private Handler mBackgroundHandler;
    private HandlerThread mBackgroundThread;
    //构造方法参数
    private final Context context;
    private TextureRegistry.SurfaceTextureEntry flutterTexture;
    //变量
    private boolean isFlashOpen = false;
    private boolean pausedPreview;
    private String currentCameraId;
    private final CameraDevice.StateCallback openCameraCallback = new OnCameraDeviceCallback();
    private CameraCharacteristics characteristics;
    private CameraDevice cameraDevice;
    private ImageReader imageReader;
    private final ImageReader.OnImageAvailableListener mOnJpegImageAvailableListener = new OnImageAvailableListenerImpl();
    private CameraCaptureSession captureSession;
    private CaptureRequest.Builder previewRequestBuilder;
    private Size previewSize;
    //private final TextureView.SurfaceTextureListener surfaceTextureListener = new OnSurfaceTextureListener();
    private final CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback(){};
    private final OnOcrRectErrorCallback errorCallback;
    //是否销毁
    private boolean isDispose;



    public CameraView(Context context, TextureRegistry.SurfaceTextureEntry flutterTexture, boolean isCameraFont,
                      OnOcrRectErrorCallback errorCallback){
        this.context = context;
        this.flutterTexture = flutterTexture;
        this.errorCallback = errorCallback;
        orientationListener = new OcrOrientationEventListener(context);
        currentCameraId = isCameraFont ? CAMERA_FRONT : CAMERA_BACK;
        isDispose = false;
        startBackgroundThread();
    }

    public void close(){
//        OcrLogger.d("----close  isDestroy",isDestroy());
        closeCaptureSession();
        cameraDevice = CameraUtils.close(cameraDevice);
        if(imageReader != null){
            imageReader.setOnImageAvailableListener(null,null);
        }
        imageReader = CameraUtils.close(imageReader);
        stopBackgroundThread();
    }

    @Override
    public void setImageListener(OnImageListener listener) {
        mOnImageListener = listener;
    }

    @Override
    public int getNumberOfCameras() {
        return Camera.getNumberOfCameras();
    }

    @Override
    public void switchCamera() {
//        OcrLogger.d("----switchCamera  isDestroy",isDestroy());
        if (currentCameraId.equals(CAMERA_BACK)) {
            currentCameraId = CAMERA_FRONT;
        } else {
            currentCameraId = CAMERA_BACK;
        }
        close();
        startBackgroundThread();
        openCamera(currentCameraId);
    }

    @Override
    public boolean toggleFlashLight() {
        if(captureSession == null || previewRequestBuilder == null) return false;

        previewRequestBuilder.set(CaptureRequest.FLASH_MODE,
                isFlashOpen ? CaptureRequest.FLASH_MODE_OFF : CaptureRequest.FLASH_MODE_TORCH);
        refreshPreviewCaptureSession(()->isFlashOpen = !isFlashOpen,(c,msg)-> {
            isFlashOpen = false;
            errorCallback("cameraFlash","闪光灯切换失败");
        });
        return isFlashOpen;
    }

    @Override
    public void takePicture() {
        if(captureSession == null || cameraDevice == null || isDestroy()) return;
//        OcrLogger.d("----takePicture  isDestroy",isDestroy());
        try {
            CaptureRequest.Builder captureBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
            captureBuilder.addTarget(imageReader.getSurface());
            captureBuilder.set(CaptureRequest.FLASH_MODE,
                    isFlashOpen ? CaptureRequest.FLASH_MODE_TORCH : CaptureRequest.FLASH_MODE_OFF);
            //captureBuilder.set(CaptureRequest.SCALER_CROP_REGION, picRect);
//            CameraUtils.setup3AControlsLocked(captureBuilder, characteristics);
            //执行拍照动作
            captureSession.capture(captureBuilder.build(), null, mBackgroundHandler);
        } catch (CameraAccessException e) {
            errorCallback("cameraPicture","拍照失败");
            OcrLogger.e(e);
        } catch (Exception e){
            OcrLogger.d(e);
        }
    }

    @Override
    public boolean isCameraFacingBack() {
        return CAMERA_BACK.equals(currentCameraId);
    }

//    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    public void pause(){
//        OcrLogger.d("pause isDispose ",isDispose,mBackgroundThread != null);
        if(mBackgroundThread == null || isDispose) return;
        close();
    }

//    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    public void resume(){
//        OcrLogger.d("resume isDispose ",isDispose,mBackgroundThread != null);
        //防止重复调用方法
        if(mBackgroundThread != null || isDispose) return;
        startBackgroundThread();
        openCamera();
    }

    public void startBackgroundThread(){
        if(mBackgroundThread == null){
            //开启后台进程
            mBackgroundThread = new HandlerThread("OcrCamera");
            mBackgroundThread.setUncaughtExceptionHandler((t,e)->{
                OcrLogger.d("ocrRectView3 camera thread error",e);
            });
            try {
                mBackgroundThread.start();
            } catch (Exception e) {
                OcrLogger.d(e);// Ignore exception in case the thread has already started.
            }
            mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
        }
        if(orientationListener != null)orientationListener.enable();
    }

    public void stopBackgroundThread(){
        //停止后台进程
        if(mBackgroundThread != null){
            mBackgroundThread.quitSafely();
            try {
                mBackgroundThread.join();
            } catch (Exception e) {
                OcrLogger.d(e);
            }
        }
        mBackgroundThread = null;
        mBackgroundHandler = null;
        if(orientationListener != null)orientationListener.disable();
    }

    public void openCamera(){
        openCamera(currentCameraId);
    }

    @SuppressLint("MissingPermission")
    private void openCamera(String cameraId) {
        OcrLogger.w("open camera id..", cameraId);
        isFlashOpen = false;

        CameraManager manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
        try {
            characteristics = manager.getCameraCharacteristics(cameraId);
            manager.openCamera(cameraId, openCameraCallback, mBackgroundHandler);
        }catch (Exception e) {
            errorCallback("cameraAccess","相机开启失败！");
            OcrLogger.d(e);
        }
    }

    public int getPreviewWidth(){
        return previewSize == null ? 0 : previewSize.getWidth();
    }
    public int getPreviewHeight(){
        return previewSize == null ? 0 : previewSize.getHeight();
    }

    public Size getPreviewSize(){
        return previewSize;
    }
    /**
     * 开启相机后，初始化参数
     */
    private void initCamera() throws CameraAccessException {
        if(characteristics == null) {
            return;
        }
        StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
        if(map == null) return;

        //全屏：屏幕的高宽比
        DisplayMetrics dm = context.getResources().getDisplayMetrics();
        int max = Math.max(dm.heightPixels,dm.widthPixels);
        int min = Math.min(dm.heightPixels,dm.widthPixels);
        float ratio = 1.0f * max / min;
        //OcrLogger.d("pixel height*width",dm.heightPixels,dm.widthPixels,ratio);
        // For still image captures, we always use the largest available size.
//        Size largestJpeg = CameraUtils.findFitSize(ratio, map.getOutputSizes(ImageFormat.JPEG));
//        previewSize = CameraUtils.findFitSize(ratio, map.getOutputSizes(SurfaceTexture.class));
        //使用上面的，会取最大的，图片5MB多
        if(max == dm.heightPixels || min < 1200){
            previewSize = CameraUtils.findLimitSize(ratio, map.getOutputSizes(SurfaceTexture.class),
                    new Size(max,min),null,new Size(1280,720));
        }else {
            previewSize = CameraUtils.findLimitSize(ratio, map.getOutputSizes(SurfaceTexture.class),
                    new Size(max,min),null,new Size(2048,1280));
        }
//        previewSize = CameraUtils.findSize(ratio,map.getOutputSizes(SurfaceTexture.class),new Size(1920,960),null,new Size(1280,720));
//        OcrLogger.d("pixel height*width",dm.heightPixels,dm.widthPixels,ratio,"previewSize",previewSize);
        CameraUtils.close(imageReader); //清理旧的
        imageReader = ImageReader.newInstance(previewSize.getWidth(),
                previewSize.getHeight(), ImageFormat.YUV_420_888, 2);
        imageReader.setOnImageAvailableListener(mOnJpegImageAvailableListener, mBackgroundHandler);


        startPreview();
    }

    public void startPreview() throws CameraAccessException {
        if (imageReader == null || imageReader.getSurface() == null || isDispose) return;
        createCaptureSession(imageReader.getSurface());
    }

    /** Pause the preview from dart. */
    public void pausePreview() throws CameraAccessException {
        if(isDispose) return;
        this.pausedPreview = true;
        this.captureSession.stopRepeating();
    }

    /** Resume the preview from dart. */
    public void resumePreview() {
        if(isDispose || captureSession == null) return;
        this.pausedPreview = false;
        this.refreshPreviewCaptureSession(
                null, (code, message) -> {
                    errorCallback("cameraAccess","相机预览失败");
                    OcrLogger.d("resumePreview fresh error",code,message);
                });
    }

    public boolean isDestroy(){
        return isDispose;
    }

    private void createCaptureSession(Surface... surfaces)
            throws CameraAccessException {
        createCaptureSession(null, surfaces);
    }

    private void createCaptureSession(Runnable onSuccessCallback, Surface... surfaces) throws CameraAccessException {
        // Close any existing capture session.
        closeCaptureSession();

        // Create a new capture builder.
        previewRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
        // Build Flutter surface to render to.
        SurfaceTexture surfaceTexture = flutterTexture.surfaceTexture();
        surfaceTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
        Surface flutterSurface = new Surface(surfaceTexture);
        previewRequestBuilder.addTarget(flutterSurface);

        // Prepare the callback.
        CameraCaptureSession.StateCallback callback =
                new CameraCaptureSession.StateCallback() {
                    @Override
                    public void onConfigured(@NonNull CameraCaptureSession session) {
                        // Camera was already closed.
                        if (cameraDevice == null) {
                            errorCallback("cameraAccess","相机已关闭");
                            //dartMessenger.sendCameraErrorEvent("The camera was closed during configuration.");
                            return;
                        }
                        captureSession = session;
//                        updateBuilderSettings(previewRequestBuilder);
                        refreshPreviewCaptureSession(onSuccessCallback,
                                (code, message) -> errorCallback("cameraAccess","相机开启预览失败"));
                    }

                    @Override
                    public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
//                        dartMessenger.sendCameraErrorEvent("Failed to configure camera session.");
                        errorCallback("cameraAccess","相机刷新预览失败");
                    }
                };

        List<Surface> remainingSurfaces = Arrays.asList(surfaces);
        // Start the session.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            // Collect all surfaces to render to.
            List<OutputConfiguration> configs = new ArrayList<>();
            configs.add(new OutputConfiguration(flutterSurface));
            for (Surface surface : remainingSurfaces) {
                configs.add(new OutputConfiguration(surface));
            }
            createCaptureSessionWithSessionConfig(configs, callback);
        } else {
            // Collect all surfaces to render to.
            List<Surface> surfaceList = new ArrayList<>();
            surfaceList.add(flutterSurface);
            surfaceList.addAll(remainingSurfaces);
            createCaptureSession(surfaceList, callback);
        }
    }

    @TargetApi(Build.VERSION_CODES.P)
    private void createCaptureSessionWithSessionConfig(
            List<OutputConfiguration> outputConfigs, CameraCaptureSession.StateCallback callback)
            throws CameraAccessException {
        cameraDevice.createCaptureSession(
                new SessionConfiguration(
                        SessionConfiguration.SESSION_REGULAR,
                        outputConfigs,
                        Executors.newSingleThreadExecutor(),
                        callback));
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    @SuppressWarnings("deprecation")
    private void createCaptureSession(
            List<Surface> surfaces, CameraCaptureSession.StateCallback callback)
            throws CameraAccessException {
        cameraDevice.createCaptureSession(surfaces, callback, mBackgroundHandler);
    }

    /**
     * Updates the builder settings with all of the available features.
     * @param requestBuilder request builder to update.
     */
    private void updateBuilderSettings(CaptureRequest.Builder requestBuilder) {
//        for (CameraFeature feature : cameraFeatures.getAllFeatures()) {
//            Log.d(TAG, "Updating builder with feature: " + feature.getDebugName());
//            feature.updateBuilder(requestBuilder);
//        }
    }

    // Send a repeating request to refresh  capture session.
    private void refreshPreviewCaptureSession(@Nullable Runnable onSuccessCallback,@NonNull OnOcrRectErrorCallback callback){
        if (captureSession == null) {
            OcrLogger.i("[refreshPreviewCaptureSession] captureSession not yet initialized, "
                            + "skipping preview capture session refresh.");
            callback.callback("cameraAccess","相机未初始化，无法刷新预览");//"captureSession is null"
            return;
        }

        try {
            if (!pausedPreview) {
                captureSession.setRepeatingRequest(previewRequestBuilder.build(), captureCallback, mBackgroundHandler);
            }

            if (onSuccessCallback != null) {
                onSuccessCallback.run();
            }

        } catch (CameraAccessException e) {
            callback.callback("cameraAccess","相机刷新预览失败"+e.getMessage());
            OcrLogger.e(e);
            //onErrorCallback.onError("cameraAccess", e.getMessage());
        } catch (Exception e){
            OcrLogger.d(e);
        }
    }

    private void closeCaptureSession() {
        if (captureSession != null) {
            OcrLogger.i("closeCaptureSession");
            captureSession.close();
            captureSession = null;
        }
    }

    public void dispose() {
        OcrLogger.d( "dispose");
        close();
        if(flutterTexture != null)flutterTexture.release();
        flutterTexture = null;
        isDispose = true;
    }

    private class OnImageAvailableListenerImpl implements ImageReader.OnImageAvailableListener {
        private byte[] y;
        private byte[] u;
        private byte[] v;
        private final ReentrantLock lock = new ReentrantLock();
        private byte[] nv21;

        @Override
        public void onImageAvailable(ImageReader reader) {
            Image image = reader.acquireLatestImage();
//            OcrLogger.d("onImageAvailable getImageFormat",reader.getImageFormat(),image == null);
            if (image == null) return;
            //OcrLogger.d("image format",reader.getImageFormat(),ImageFormat.JPEG,ImageFormat.YUV_420_888);
            byte[] bytes;
            try {
                bytes = handleImage(image,reader.getImageFormat());
            }catch (Exception e){
                OcrLogger.d(e);
                bytes = new byte[0];
            }finally {
                image.close();
            }
            back(bytes);
        }

        /**
         * 处理图片数据
         */
        private byte[] handleImage(Image image,int format){
            byte[] bytes;
            if(format == ImageFormat.JPEG){
                ByteBuffer buffer = image.getPlanes()[0].getBuffer();
                bytes = new byte[buffer.remaining()];
                buffer.get(bytes);
            } else if (format == ImageFormat.YUV_420_888) {
                //ImageFormat.YUV_420_888  Y:U:V == 4:2:2  https://my.oschina.net/u/3970172/blog/3071603
                Image.Plane[] planes = image.getPlanes();
                ByteBuffer buffer0 = planes[0].getBuffer();
                ByteBuffer buffer1 = planes[1].getBuffer();
                ByteBuffer buffer2 = planes[2].getBuffer();
                // 加锁确保y、u、v来源于同一个Image
                lock.lock();
                // 重复使用同一批byte数组，减少gc频率
                if (y == null || buffer0.remaining() != y.length) {
                    //切换前后摄像头时候
                    if(y != null && buffer0.remaining() != y.length){
                        OcrLogger.d("buffer0.remaining()",buffer0.remaining(),"y.length",y.length);
                    }
                    y = new byte[buffer0.limit() - buffer0.position()];
                    u = new byte[buffer1.limit() - buffer1.position()];
                    v = new byte[buffer2.limit() - buffer2.position()];
                }

                if (buffer0.remaining() == y.length) {
                    buffer0.get(y);
                    buffer1.get(u);
                    buffer2.get(v);
                    bytes = onPreview(y, u, v, planes[0].getRowStride(),previewSize.getWidth(),previewSize.getHeight());
                    //该方式，不会花屏，但是上面条件时，出现ocr无法识别
//                    bytes = OcrUtil.getBytesFromImageAsType(image,OcrUtil.NV21);
//                    OcrLogger.d("onImageAvailable mOnYuvImageListener",image.getWidth(),planes[0].getRowStride(),previewSize,bytes.length,y.length,u.length);
                }else {
                    OcrLogger.d("image available handleImage error, buffer0.remaining()",buffer0.remaining(),"y.length",y.length);
                    bytes = new byte[0];
                }
                lock.unlock();
            }else {
                OcrLogger.d("image available handleImage error, format",format);
                bytes = new byte[0];
            }
            return bytes;
        }

        //该方式，在image.getWidth()<plane.getRowStride()时保存的图片为绿色花屏，如800*400，因此现在preivewSize为1280*720
        private byte[] onPreview(byte[] y, byte[] u, byte[] v, Size previewSize, int rowStride) {
            if (nv21 == null) {
                nv21 = new byte[rowStride * previewSize.getHeight() * 3 / 2];
            }
            //OcrLogger.d("preview ",previewSize,rowStride,previewSize.getHeight());
            // 回传数据是YUV422
            if (y.length / u.length == 2) {
                OcrUtil.yuv422ToYuv420sp(y, u, v, nv21, rowStride, previewSize.getHeight());
            }
            // 回传数据是YUV420
            else if (y.length / u.length == 4) {
                OcrUtil.yuv420ToYuv420sp(y, u, v, nv21, rowStride, previewSize.getHeight());
            }
            return nv21;
        }

        private byte[] yuvByte;
        private byte[] uByte;
        private byte[] vByte;

        private byte[] onPreview(byte[] y, byte[] u, byte[] v
                , int rowStride
                , int imageW,int imageH){
            int total = imageW * imageH;
            int yLength = total *ImageFormat.getBitsPerPixel(ImageFormat.YUV_420_888) / 8;
            if(yuvByte == null || yuvByte.length != yLength){
                yuvByte = new byte[yLength];
                uByte = new byte[total / 4];
                vByte = new byte[total / 4];
            }
            int srcIndex = 0;
            int dstIndex = 0;
            //取出有效的y
            for (int i = 0 ; i < imageH ; i++){
                //每次复制 一行
                System.arraycopy(y, srcIndex, yuvByte, dstIndex, imageW);
                srcIndex += rowStride;
                dstIndex += imageW;
            }
            //取出uByte，重置起点
            srcIndex = 0;
            int halfHeight = imageH / 2;
            int halfWidth = imageW / 2;
            int index = 0;
            for (int i = 0 ; i < halfHeight ; i++){
                //根据一行处理
                for (int j = 0 ; j <  halfWidth ; j++){
                    uByte[index++] = u[srcIndex];
                    srcIndex += 2;//pixelsStride = 2
                }
                srcIndex += rowStride - imageW;
            }
            //取出vByte，重置起点
            srcIndex = 0;
            index = 0;
            for (int i = 0 ; i < halfHeight ; i++){
                //根据一行处理
                for (int j = 0 ; j <  halfWidth ; j++){
                    vByte[index++] = v[srcIndex];
                    srcIndex += 2;//pixelsStride = 2
                }
                srcIndex += rowStride - imageW;
            }
            //处理uv到 yuv
            for (int i = 0; i < vByte.length; i++) {
                yuvByte[dstIndex++] = vByte[i];
                yuvByte[dstIndex++] = uByte[i];
            }
            return yuvByte;
        }

        void back(byte[] bytes) {
            if (cameraDevice == null) {
                OcrLogger.d("ocr camera device is null ");
                return;
            }
            if (mOnImageListener != null && bytes != null) {
                mOnImageListener.onImage(bytes, getImageRotation());
            }
        }
    }

    ///获取图片需要旋转角度
    private int getImageRotation(){
        if(orientationListener == null)return 0;
        orientationListener.setCameraFacingBack(isCameraFacingBack());
        return orientationListener.getRotation();
    }
    /**
     * 相机异常回调
     */
    void errorCallback(String code,String msg){
        if(errorCallback != null) errorCallback.callback(code,msg);
    }
    /**
     * 相机开启 回调
     */
    private class OnCameraDeviceCallback extends CameraDevice.StateCallback {
        @Override
        public void onOpened(@NonNull CameraDevice camera) {
            cameraDevice = camera;
            try {
                initCamera();
            } catch (CameraAccessException e) {
                errorCallback("cameraError","相机开启失败");
                OcrLogger.d(e);
            } catch (Exception e){
                OcrLogger.d(e);
            }
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice camera) {
            cameraDevice = CameraUtils.close(cameraDevice);
        }

        @Override
        public void onError(@NonNull CameraDevice camera, int error) {
            onDisconnected(camera);
            errorCallback("cameraError","相机异常"+error);
            Log.e("camera", "error : " + error);
        }

        @Override
        public void onClosed(@NonNull CameraDevice camera) {
            super.onClosed(camera);
            OcrLogger.d("cameraDevice onClosed callback,current cameraDevice is null",cameraDevice == null);
            cameraDevice = null;
        }
    }
}
